﻿using System;
using System.Linq;
using Boxi.Extensions;
using System.Collections.Generic;

namespace Boxi.Model
{
    public class MoveOptionList
    {

        /// <summary>
        /// 
        /// </summary>
        public MoveModel this[Byte i]
        {
            get
            {
                if (i >= Length)
                    throw new Exception(String.Format("index {0} can not be called", i));

                return _moves[i];
            }
            set
            {
                if (i >= Length)
                    throw new Exception(String.Format("index {0} can not be set", i));

                _moves[i] = value;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public MoveModel this[Int32 i]
        {
            get
            {
                return this[Convert.ToByte(i)];
            }
            set
            {
                this[Convert.ToByte(i)] = value;
            }
        }

        private MoveModel[] _moves;
        private Byte _columns;
        private Byte _rows;
        #region Properties
        public Byte Length
        {
            get
            {
                return (Byte)_moves.Length;
            }
        }
        #endregion
        #region Constructors
        /// <summary>
        /// 
        /// </summary>
        /// <param name="moves"></param>
        public MoveOptionList(MoveModel[] moves)
        {
            _moves = moves;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="columns"></param>
        /// <param name="rows"></param>
        public MoveOptionList(Byte columns, Byte rows)
        {
            _columns = columns;
            _rows = rows;
            _moves = new MoveModel[((columns + 1) * rows) + (columns * (rows + 1))];
            Byte compleateColumn = (Byte)(columns * 2 + 1);
            for (Byte i = 1; i <= ((Byte)_moves.Length); i++)
            {
                Byte first = 0;
                Byte second = 0;
                Byte columnPosition = (Byte)(i % (compleateColumn));

                //Horizontal
                if (columnPosition != 0 && columnPosition <= columns)
                {
                    Int32 secondTemp = columnPosition + ((i / compleateColumn) * columns);
                    if (secondTemp <= columns * rows)
                        second = (Byte)secondTemp;

                    if (i > compleateColumn)
                        first = (Byte)(secondTemp - columns);
                }
                else
                {
                    Int32 secondTemp = (columnPosition + ((i / compleateColumn) * columns) - columns);
                    if (columnPosition != 0 && (i / compleateColumn) < rows)
                        second = (Byte)secondTemp;

                    if (columnPosition > columns + 1)
                        first = (Byte)(secondTemp - 1);
                    else if (columnPosition == 0)
                        first = (Byte)(i / compleateColumn * columns);
                }
                if (first > columns * rows || second > columns * rows)
                    throw new Exception("This should never happen");
                _moves[i - 1] = new MoveModel(i, first, second);
            }
        }
        #endregion
        #region Methods
        public MoveModel GetByID(Byte id)
        {
            return this[id - 1];
        }
        public MoveOptionList Copy()
        {
            MoveModel[] result = new MoveModel[_moves.Length];
            for (Byte i = 0; i < _moves.Length; i++)
                result[i] = this[i];

            return new MoveOptionList(result);
        }
        public MoveModel[] Where(Func<MoveModel, Boolean> search)
        {
            return _moves.Where(m => m.MoveID > 0).Where(search).ToArray();
        }
        public override string ToString()
        {
            String result = String.Empty;
            for (Byte i = 0; i < _moves.Length; i++)
                result += ((result == String.Empty) ? String.Empty : ",") + _moves[i].MoveMadeOrder;

            return result;
        }
        public Boolean Contains(Byte id)
        {
            Boolean result = false;
            for (Byte i = 0; i < _moves.Length; i++)
            {
                if (_moves[i].MoveID == 0)
                {
                    break;
                }
                else if (_moves[i].MoveID == id)
                {
                    result = true;
                    break;
                }
            }
            return result;
        }
        public Int32 GetOpenMoveCount()
        {
            Byte openMoves = 0;
            for (Byte i = 0; i < _moves.Length; i++)
                if (_moves[i].MoveID == 0)
                    openMoves += 1;

            return openMoves;
        }
        public MoveModel[] GetOpenMoves()
        {
            return _moves.Where(m => m.MoveMadeOrder == 0).ToArray();
        }
        public MoveModel[] GetOpenOffensiveMoves(GameModel game)
        {
            return _moves.Where(m => m.MoveMadeOrder == 0 && _moves.GetPointsThatWillScore(m.MoveID, game).Length > 0).ToArray();
        }
        public MoveModel[] GetOpenDefensiveMoves(GameModel game)
        {
            return _moves.Where(m => m.MoveMadeOrder == 0 && IsDefensiveMove(m, game)).ToArray();
        }
        public MoveModel[] GetOpenBadMoves(GameModel game)
        {
            return _moves.Where(m => m.MoveMadeOrder == 0 && IsPointForNextPlayerNotCurrent(m, game)).ToArray();
        }
        /// <summary>
        /// Make move
        /// </summary>
        /// <param name="_moves">List of Possible Moves</param>
        /// <param name="movesMade">List of Moves Made</param>
        /// <param name="moveID">Move to Set</param>
        /// <param name="playerID">Player ID</param>
        /// <param name="game">Game Info</param>
        /// <returns>Move Order</returns>
        public Byte SetMove(MoveHistoryList movesMade, Byte moveID, SByte playerID, GameModel game)
        {
            if (movesMade.GetByID(moveID).MoveID != 0)
                throw new Exception("That move has aready been made");

            Byte[] pointsAwarded = GetPointsThatWillScore(moveID, game);
            MoveModel move = _moves[moveID - 1];
            move.MoveMadeOrder = movesMade.GetNextMoveNumber();
            _moves[move.MoveID - 1] = move;
            movesMade[move.MoveMadeOrder - 1] = new MoveHistoryModel(move, playerID, pointsAwarded);
            return move.MoveMadeOrder;
        }
        public Byte[] GetOpenMoveIDs(PointModel point)
        {
            List<Byte> openMoveIDs = new List<Byte>();
            for (Byte i = 0; i < 4; i++)
            {
                if (_moves[point[i] - 1].MoveMadeOrder == (Byte)0)
                    openMoveIDs.Add(point[i]);
            }
            return openMoveIDs.ToArray();
        }
        public Boolean WillMoveScore(Byte moveID, PointModel point)
        {
            Byte[] openMoveIDs = GetOpenMoveIDs(point);
            return (openMoveIDs.Length == 1 && openMoveIDs[0] == moveID);
        }

        public Boolean WillRelatedMoveScore(MoveModel move, Byte moveID, GameModel game)
        {
            var first = GetByID(moveID).FirstPoint;
            if (first > 0 && (new PointModel(first, game.Columns)).Contains(move.MoveID) && WillMoveScore(move, game))
                return true;

            var second = GetByID(moveID).SecondPoint;
            if (second > 0 && (new PointModel(second, game.Columns)).Contains(move.MoveID) && WillMoveScore(move, game))
                return true;

            return false;
        }
        public Boolean WillMoveScore(MoveModel move, GameModel game)
        {
            return (
                (move.FirstPoint != 0 && WillMoveScore(move.MoveID, new PointModel(move.FirstPoint, game.Columns))) ||
                (move.SecondPoint != 0 && WillMoveScore(move.MoveID, new PointModel(move.SecondPoint, game.Columns)))
             );
        }
        public Boolean IsDefensiveMove(PointModel point, Byte moveID)
        {
            Int32 movesOpen = 0;
            Byte[] mopenMoveIDs = GetOpenMoveIDs(point);
            if (mopenMoveIDs.Length > 2)
            {
                for (Byte i = 0; i < mopenMoveIDs.Length; i++)
                    if (mopenMoveIDs[i] != moveID)
                        movesOpen += 1;
            }
            return movesOpen > 1;
        }
        public Boolean IsDefensiveMove(MoveModel move, GameModel game)
        {
            if (move.FirstPoint > 0)
            {
                PointModel first = new PointModel(move.FirstPoint, game.Columns);
                if (!IsDefensiveMove(first, move.MoveID))
                    return false;
            }
            if (move.SecondPoint > 0)
            {
                PointModel second = new PointModel(move.SecondPoint, game.Columns);
                if (!IsDefensiveMove(second, move.MoveID))
                    return false;
            }
            return true;
        }
        public Byte[] GetPointsThatWillScore(Byte moveID, GameModel game)
        {
            List<Byte> points = new List<Byte>(2);
            if (_moves[moveID - 1].FirstPoint > 0)
            {
                PointModel first = new PointModel(_moves[moveID - 1].FirstPoint, game.Columns);
                if (WillMoveScore(moveID, first))
                    points.Add(first.PointID);
            }
            if (_moves[moveID - 1].SecondPoint > 0)
            {
                PointModel second = new PointModel(_moves[moveID - 1].SecondPoint, game.Columns);
                if (WillMoveScore(moveID, second))
                    points.Add(second.PointID);
            }
            return points.ToArray();
        }
        public bool IsPointForNextPlayerNotCurrent(MoveModel move, GameModel game)
        {
            Byte result = 0;
            if (move.FirstPoint > 0)
            {
                var first = new PointModel(move.FirstPoint, game.Columns);
                Byte[] openMoveIDs = GetOpenMoveIDs(first);
                if (openMoveIDs.Length == 1 && openMoveIDs[0] == move.MoveID)
                    return false;
                else if (openMoveIDs.Length == 2 && openMoveIDs.Contains(move.MoveID))
                    result += 1;
            }
            if (move.SecondPoint > 0)
            {
                var second = new PointModel(move.SecondPoint, game.Columns);
                Byte[] openMoveIDs = GetOpenMoveIDs(second);
                if (openMoveIDs.Length == 1 && openMoveIDs[0] == move.MoveID)
                    return false;
                else if (openMoveIDs.Length == 2 && openMoveIDs.Contains(move.MoveID))
                    result += 1;
            }
            return result > 0;
        }
        #endregion
    }
}
